<?php
/**
 * 
 * @author	ronnie  (comdeng@gmail.com)
 * @link    http://fillphp.com/
 * @date   	2014-5-1
 * @file	Request.php
 * @description 
 */
namespace fillphp\page;
require_once dirname(__DIR__).'/hook/Hook.php';

const FORMAT_TEXT 		= 'text';
const FORMAT_HTML 		= 'html';
const FORMAT_XML 		= 'xml';
const FORMAT_JSON 		= 'json';
const FORMAT_JAVASCRIPT = 'javascript';
const FORMAT_XLS 		= 'xls';
const METHOD_GET 		= 'GET';
const METHOD_POST 		= 'POST';
const METHOD_PUT 		= 'PUT';
const METHOD_DELETE 	= 'DELETE';
const METHOD_HEAD 		= 'HEAD';

class Request
{
	var $host;				// 请求的主机
	var $method;			// 请求方法，对应POST方法中的_method参数
	var $request_uri;		// 原始请求URI
	var $uri;				// 使用的URI
	var $request_url;		// 原始请求的URL
	var $url;				// 实际使用的URL
	var $request_method; 	// 原始请求方法
	var $referer = '';		// 请求来源
	var $ip;				// 客户端ip
	
	var $format = FORMAT_HTML;	// 输出格式
	var $charset = 'UTF-8';		// 输出编码
	
	private static $magicArgs = array(
		'_of' => 'format',
		'_oc' => 'charset',
	);
	
	function __construct($properties = array(), $inputs = array())
	{
		foreach($properties as $key=>$prop) {
			if (property_exists('\fillphp\page\Request', $key)) {
				$this->$key = $prop;
			}
		}
		foreach($inputs as $key => $vals) {
			if (isset($this->inputs[$key])) {
				$this->inputs[$key] = $vals;
			}
		}
	}
	
	var $inputs = array(
		'gets' 		=> array(),
		'posts' 	=> array(),
		'cookies'	=> array(),
		'files'		=> array(),
		'servers'	=> array(),
		'args'		=> array(),
	);
	
	
	/**
	 * 获取get值
	 * @param string $key
	 * @param string $defValue
	 */
	function get($key, $defValue = null)
	{
		return isset($this->inputs['gets'][$key]) ? $this->inputs['gets'][$key] : $defValue;
	}	
	
	/**
	 * 获取post值
	 * @param string $key
	 * @param string $defValue
	 */
	function post($key, $defValue = null)
	{
		return isset($this->inputs['posts'][$key]) ? $this->inputs['posts'][$key] : $defValue;
	}
	
	/**
	 * 获取cookie值
	 * @param unknown $key
	 * @param string $defValue
	 */
	function cookie($key, $defValue = null)
	{
		return isset($this->inputs['cookies'][$key]) ? $this->inputs['cookies'][$key] : $defValue;
	}
	
	/**
	 * 获取请求值，顺序依次为get、post、cookie
	 * @param string $key
	 * @param string $defValue
	 */
	function request($key, $defValue = null)
	{
		return $this->get($key, 
			$this->post($key,
				$this->cookie($key, $defValue)
			)
		);
	}
	
	/**
	 * 获取文件
	 * @param string $key
	 */
	function file($key)
	{
		return isset($this->inputs['files'][$key]) ? $this->inputs['files'][$key] : $defValue;
	}
	
	/**
	 * 获取服务器端变量
	 * @param string $key
	 */
	function server($key)
	{
		return isset($this->inputs['servers'][$key]) ? $this->inputs['servers'][$key] : $defValue;
	}
	
	/**
	 * 是否为ajax请求
	 * @return boolean
	 */
	function isXMLHttpRequest()
	{
		return 'XMLHttpRequest' === $this->server('X-Requested-With');
	}
	
	/**
	 * 是否有通过网址传递的参数
	 * @return boolean
	 */
	function hasArgs()
	{
		return !empty($this->inputs['args']);
	}
	
	static function buildFromGlobal()
	{
		$props = self::buildProps($_SERVER, $_GET, $_POST);
		
		$inputs = array(
			'gets' 		=> $_GET,
			'posts' 	=> $_POST,
			'servers' 	=> $_SERVER,
			'cookies'	=> $_COOKIE,
			'files'		=> $_FILES,
		);
		
		// 针对request的请求参数做初始化
		$hooks = \fillphp\hook\Hook::gets(\fillphp\hook\HOOK_REQUEST_INPUT);
		foreach($hooks as $hook) {
			call_user_func_array($hook, array($inputs));
		}
		
		return new static($props, $inputs);
	}
	
	/**
	 * 构建好需要用到的属性
	 * @param array $servers
	 * @param array $gets
	 * @param array $posts
	 * @param boolean $route
	 * @return array
	 */
	static function buildProps($servers, $gets = array(), $posts = array(), $route = true)
	{
		$props = array();
		// 只有GET方法才开启路由
		$props['method'] = $props['request_method'] = $servers['REQUEST_METHOD'];
		$props['uri'] = $props['request_uri'] = $servers['REQUEST_URI'];
		$props['url'] = $props['request_url'] = parse_url($props['request_uri'],  PHP_URL_PATH);
		$props['host'] = $servers['HTTP_HOST'];
		
		// 只有在请求ActionController时，且请求方法为get请求，才会允许做路由的重写
		if ($route && $props['method'] == METHOD_GET) {
			$hooks = \fillphp\hook\Hook::gets(\fillphp\hook\HOOK_REQUEST_ROUTE);
			if (!empty($hooks)) {
				$props['url'] = call_user_func_array($hooks[0], array($props['request_url'], $props['host']));
				if ($props['url'] != $props['request_url']) {
					$pos = strpos($props['url'], '?');
					if ($pos !== false) {
						if ($servers['QUERY_STRING']) {
							$servers['QUERY_STRING'] .= '&'.substr($props['url'], $pos + 1);
						} else {
							$servers['QUERY_STRING'] = substr($props['url'], $pos + 1);
						}
						$props['url'] = substr($props['url'], 0, $pos);
					}
					$props['uri'] = $props['url'] . ($servers['QUERY_STRING'] ? '?'.$servers['QUERY_STRING'] : '');
				}
			}
		}

		if ($props['method'] === \fillphp\page\METHOD_POST) {
			$m = empty($gets['_method']) ? ( empty($posts['_method']) ? '' : $posts['_method'] ) : $gets['_method'];
			if ($m) {
				$m = strtoupper($m);
				if (in_array($m, array(\fillphp\page\METHOD_PUT, \fillphp\page\METHOD_DELETE))) {
					$props['method'] = $m;
				}
			}
		}
		
		if (isset($servers['HTTP_REFERER'])) {
			$props['HTTP_REFERER'] = $servers['HTTP_REFERER'];
		}
		
		// 获取用户访问ip
		if (!empty($servers['HTTP_X_REAL_IP'])) {
			$props['ip'] = $servers['HTTP_X_REAL_IP'];
		} elseif (!empty($servers['REMOTE_ADDR'])) {
			$props['ip'] = $servers['REMOTE_ADDR'];
		}
		
		// 将魔术参数赋值
		foreach(self::$magicArgs as $key => $prop) {
			if (isset($gets[$key])) {
				$this->$prop = $gets[$key];
			}
		}
		
		return $props;
	}
	
	/**
	 * 创建一个请求
	 * @param string $uri
	 * @param string $method
	 * @param array $params
	 * @param array $cookies
	 * @param array $files
	 * @param array $server
	 * @return \fillphp\page\Request
	 */
	static function build($uri, 
			$method = METHOD_GET, 
			$params = array(), 
			$cookies = array(), 
			$files = array(), 
			$servers = array())
	{
		$servers = array_replace(array(
			'SERVER_NAME'          => 'localhost',
			'SERVER_PORT'          => 80,
			'HTTP_HOST'            => 'localhost',
			'HTTP_USER_AGENT'      => 'fillphp/1.0',
			'HTTP_ACCEPT'          => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
			'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5',
			'HTTP_ACCEPT_CHARSET'  => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
			'REMOTE_ADDR'          => '127.0.0.1',
			'SCRIPT_NAME'          => '',
			'SCRIPT_FILENAME'      => '',
			'SERVER_PROTOCOL'      => 'HTTP/1.1',
			'REQUEST_TIME'         => time(),
		), $servers);
		
		$servers['PATH_INFO'] = '';
		$servers['REQUEST_METHOD'] = strtoupper($method);
		
		$info = parse_url($uri);
		if (isset($info['host'])) {
			$servers['SERVER_NAME'] = $info['host'];
			$servers['HTTP_HOST'] = $info['host'];
		}
		
		if (isset($info['scheme'])) {
			if ('https' === $info['scheme']) {
				$servers['HTTPS'] = 'on';
				$servers['SERVER_PORT'] = 443;
			} else {
				unset($servers['HTTPS']);
				$servers['SERVER_PORT'] = 80;
			}
		}
		
		if (isset($info['port'])) {
			$servers['SERVER_PORT'] = $info['port'];
			$servers['HTTP_HOST'] = $servers['HTTP_HOST'].':'.$info['port'];
		}
		
		if (!isset($info['path'])) {
			$info['path'] = '/';
		}
		
		$gets = array();
		switch (strtoupper($method)) {
			case 'POST':
			case 'PUT':
			case 'DELETE':
				if (!isset($servers['CONTENT_TYPE'])) {
					$servers['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
				}
			case 'PATCH':
				$posts = $params;
				break;
			default:
				$posts = array();
				$gets = $params;
				break;
		}
		
		$queryString = '';
		if (isset($info['query'])) {
			parse_str(html_entity_decode($info['query']), $qs);
		
			if ($gets) {
				$gets = array_replace($qs, $gets);
				$queryString = http_build_query($gets, '', '&');
			} else {
				$gets = $qs;
				$queryString = $info['query'];
			}
		} elseif ($gets) {
			$queryString = http_build_query($gets, '', '&');
		}
		
		$servers['REQUEST_URI'] = $info['path'].('' !== $queryString ? '?'.$queryString : '');
		$servers['QUERY_STRING'] = $queryString;
		
		$props = self::buildProps($servers, $gets, $posts, false);
		$inputs = array(
			'gets' 		=> $gets,
			'posts'		=> $posts,
			'cookies'	=> $cookies,
			'servers'	=> $servers,
			'files'		=> $files,			
		);
		
		return new static($props, $inputs);
	}
}